寫測試可以幫助看 code 的人(可能是別人、也可能是未來的自己)明確暸解一個功能究竟能接受哪些參數、又會有哪幾種可能的輸出內容,也保障一個功能在「單元測試有覆蓋到的部分」是有品質保證的。為了能在晚上睡得更安穩,能從元件中獨立出來的邏輯就盡量提供配合的單元測試吧。
於是今天的主題就是:如何為白手起家的 React TypeScript app 專案設定 jest ( つ•̀ω•́)つ
在寫這篇鐵人賽文章的當下 jest 版本為 29.6,為了在這版 jest 順利把 TypeScript 測試跑起來,你需要安裝以下套件:
yarn add -D @jest/globals jest ts-jest ts-node
除了 jest
以外都是輔助 TypeScript 用的套件。如果在你閱讀這篇文章時 jest 的版本已遠超 29.6,建議在安裝完 jest
後,根據執行 yarn jest
後的終端訊息一個一個把需要的套件裝回去。
在專案根目錄新增一個 jest.config.ts
檔案並填入以下內容:
import type { Config } from 'jest';
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'jsdom', // 選填,見下方說明
moduleNameMapper: {
'@/jest.config': '<rootDir>/jest.config.ts',
'@Model/(.*)': '<rootDir>/src/model/$1',
'@Tool/(.*)': '<rootDir>/src/tool/$1',
},
};
export default config;
解說如下:
preset
能讓 jest 能跑 TypeScript 測試,一個是 babel
、另外一個就是今天使用的 ts-jest
document.createElement('div')
)的話,記得將 testEnvironment
從預設的 node
改成 jsdom
並安裝套件 jest-environment-jsdom
。來源:官方文件
moduleNameMapper
讓 jest 知道如何解析自訂路徑
@/jest.config
路徑時要去取 ./jest.config.ts
、而在遇到 @Model
或 @Tool
開頭的路徑時,要去 ./src/model
或 ./src/tool
資料夾尋找對應的內容(.*)
比對到的字串會用於 $1
,比如 @Tool/isValidUser
會對應到 <rootDir>/src/tool/isValidUser
.*
代表「任何字元都可以」且「允許長度從零到無限」,加上 ()
代表要把比對出來的對象 group 起來,然後透過 $1
取出被 group 起來的內容在執行 .ts
類型的單元測試時,以上的設定基本上就足夠了。
首先在 ./src/tool
中新增了一個檔案 isValidUser.ts
並撰寫以下功能:
export function isValidUser(arg: unknown) {
return (
typeof arg === 'object' && !!arg && 'userName' in arg && !!arg.userName
);
}
接著在 ./src/tool
中新增 isValidUser.test.ts
來撰寫測試腳本:
import { describe, expect, test } from '@jest/globals';
import { isValidUser } from '@Tool/isValidUser';
describe('isValidUser', () => {
test('should return false if the argument is not an object', () => {
expect(isValidUser(null)).toBe(false);
expect(isValidUser(undefined)).toBe(false);
expect(isValidUser(123)).toBe(false);
expect(isValidUser('abc')).toBe(false);
expect(isValidUser(true)).toBe(false);
expect(isValidUser(false)).toBe(false);
});
test('should return false if the argument is an empty object', () => {
expect(isValidUser({})).toBe(false);
});
test('should return false if the argument is an object without userName', () => {
expect(isValidUser({ name: 'user A' })).toBe(false);
});
test('should return false if the argument is an object with empty userName', () => {
expect(isValidUser({ userName: '' })).toBe(false);
});
test('should return true if the argument is an object with non-empty userName', () => {
expect(isValidUser({ userName: 'user A' })).toBe(true);
});
});
(純個人感受:GitHub Copilot 為簡單功能產生測試腳本的表現真的不錯,個人版方案一年 100 美金實在不算太貴,可以考慮課個金)
開啟終端,輸入 make test
(參考第 6 天)即可執行單元測試:
PASS src/tool/isValidUser.test.ts
isValidUser
✓ should return false if the argument is not an object (4 ms)
✓ should return false if the argument is an empty object
✓ should return false if the argument is an object without userName (1 ms)
✓ should return false if the argument is an object with empty userName
✓ should return true if the argument is an object with non-empty userName
個人認為 Good Code, Bad Code 一書第十章「單元測試準則」對測試做了不錯的總結。目前在撰寫單元測試時,我會盡可能讓測試腳本都能符合以下特徵:
附上個人之前的筆記,有較為細節的摘要,歡迎參考。
對 TypeScript React app 專案設定單元測試的門檻並不高,多一點保險可以改善工程師的睡眠品質,真心推薦。
本文同步發佈於普通文組 2.0